package gridy.fitness;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.Semaphore;

import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMFactory;
import org.apache.axiom.om.OMNamespace;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.client.Options;
import org.apache.axis2.client.ServiceClient;
import org.apache.log4j.Logger;
import org.osoa.sca.annotations.EagerInit;
import org.osoa.sca.annotations.Scope;

class WorkerInfo {
    public boolean anyJobSent = false;
    public int firstSentJobIndex;
    public int numOfJobsSent;
}

@EagerInit
@Scope("COMPOSITE")
public class FitnessCalculatorImpl implements FitnessCalculator {
    
    static Logger log = Logger.getLogger(FitnessCalculatorImpl.class);

    private Map<String, WorkerInfo> workers = new HashMap<String, WorkerInfo>();
    private Semaphore semaphore;
    private double[] gResult;
    
    public FitnessCalculatorImpl() {
        log.info("Tworze FitnessCalculatorImpl");
    }
    
    @Override
    public double[] calculate(double[] arguments, int numOfProblems) {
        
        this.gResult = new double[numOfProblems];
        
        assert arguments.length % numOfProblems == 0;
        int problemSize = arguments.length % numOfProblems;
        int numOfWorkers = workers.keySet().size();
        int problemsPerWorker = (int)(numOfProblems / numOfWorkers);
        int additionalProblems = numOfProblems % numOfWorkers;
        
        log.debug(String.format(
                "arguments.length = %d, numOfProblems = %d, problemSize = %d, numOfWorkers = %d, problemsPerWorker = %d",
                arguments.length,numOfProblems,problemSize,numOfWorkers,problemsPerWorker));
        
        semaphore = new Semaphore(1 - numOfWorkers);
        
        Iterator<String> workersIt = workers.keySet().iterator();
        int problemBegIndex = 0;
        for (int workerNum = 0; workerNum < numOfWorkers; workerNum++) {
            String workerName = workersIt.next();
            
            double[] job;
            int currentNumOfProblems;
            if ( (workerNum != (numOfWorkers - 1)) || (additionalProblems == 0)) {
                
                job = new double[problemsPerWorker];
                currentNumOfProblems = problemsPerWorker;
            }
            else {
                // do ostatniego wysyla sie pozostale zadania
                job = new double[problemsPerWorker+additionalProblems];
                currentNumOfProblems = problemsPerWorker+additionalProblems;
            }
            
            for (int i = 0; i < job.length; i++) {
                job[i] = arguments[problemBegIndex+i];
            }
            
            log.debug(String.format(
                    "send, wysylam do %s, job.length=%d, numOfProblems=%d, problemBegIndex=%d, problemEndIndex=%d",
                    workerName,job.length,currentNumOfProblems,problemBegIndex,problemBegIndex+currentNumOfProblems));
            
            send(workerName, job, currentNumOfProblems, problemBegIndex);
            
            problemBegIndex += problemsPerWorker;
        }
        try {
            // czeka aż workerzy zwrócą wyniki
            semaphore.acquire();
        }
        catch (InterruptedException e) {
            e.printStackTrace();
            System.exit(1);
        }
        
        log.debug("FitnessCalculatorImpl: semafor się odblokował");
        for (double r : this.gResult) {
            log.debug("Wynik "+r);
        }
        
        return gResult;
    }

    private void send(String workerAddress, double[] arguments,
            int numOfProblems, int firstProblemIndex) {
        
        log.debug(String.format(
                "Wysylam do %s, arguments.length = %d, numOfProblems = %d, firstProblemIndex = %d",
                workerAddress, arguments.length, numOfProblems, firstProblemIndex));
        
        try {
            
            ServiceClient sc = new ServiceClient();
            Options opts = new Options();
            opts.setTo(new EndpointReference("http://"+workerAddress+":8085/FitnessCalculatorWorkerService"));
            sc.setOptions(opts);
            
            OMFactory fac = OMAbstractFactory.getOMFactory();
            OMNamespace omNs = fac.createOMNamespace(
                    "http://ws.apache.org/axis2/xsd", "ns1");
            OMElement payload = fac.createOMElement("calculate", omNs);
            OMElement value;
            for (double arg : arguments) {
                value = fac.createOMElement("arg0", omNs);
                value.setText(String.valueOf(arg));
                payload.addChild(value);
            }
            value = fac.createOMElement("arg1", omNs);
            value.setText(String.valueOf(numOfProblems));
            payload.addChild(value);
            
            value = fac.createOMElement("arg2", omNs);
            value.setText(String.valueOf(firstProblemIndex));
            payload.addChild(value);
                
            sc.fireAndForget(payload);
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void register(String workerAddress) {
        
        log.info("Worker '"+workerAddress+"' wywołał register");
        for (String worker : workers.keySet()) {
            log.info("\tByl juz worker: "+worker);
        }
        workers.put(workerAddress, new WorkerInfo());
    }

    @Override
    public void putResults(double[] result, int firstProblemIndex) {
        
        log.debug(String.format("Otrzymalem wyniki, result.length = %d, firstProblemIndex = %d",
                result.length, firstProblemIndex));
        
        for (int i = 0; i < result.length; i++) {
            this.gResult[firstProblemIndex+i] = result[i];
        }
        semaphore.release();
    }
}
